#include "xlcomm/base/file.h"

#include "xlcomm/base/logger.h"

namespace xlcomm {

#define FILE_CHECK_FILEPTR(return_value) \
  {                                      \
    if (file_ == nullptr) {              \
      LOG_WARN << "file is not opened!"; \
      return return_value;               \
    }                                    \
  }

File::File() : file_(nullptr) {}

File::File(const char* file_path, const char* mode) : file_(nullptr) {
  Open(file_path, mode);
}

File::~File() {
  if (file_ != nullptr) Close();
}

bool File::Open(const char* file_path, const char* mode) {
  file_ = fopen(file_path, mode);
  if (file_ == nullptr) {
    LOG_SYSERROR(fopen) << "file_path:" << file_path << " mode:" << mode;
    return false;
  }
  file_path_ = file_path;
  return true;
}

bool File::Close() {
  FILE_CHECK_FILEPTR(true);
  if (fclose(file_) != 0) {
    LOG_SYSERROR(fclose) << "file_path:" << file_path_;
    return false;
  }
  file_ = nullptr;
  return true;
}

bool File::Seek(off_t offset, int whence) {
  FILE_CHECK_FILEPTR(false);
  XLCOMM_ASSERT(whence == SEEK_SET || whence == SEEK_CUR || whence == SEEK_END);
#ifdef _WIN32
  if (_fseeki64(file_, offset, whence) != 0) {
#else
  if (fseeko(file_, offset, whence) != 0) {
#endif  // _WIN32
    LOG_SYSERROR(fseek) << "file_path:" << file_path_;
    return false;
  }
  return true;
}

int64_t File::Tell() {
  FILE_CHECK_FILEPTR(-1);
  int64_t pos = 0;
#ifdef _WIN32
  pos = _ftelli64(file_);
#else
  pos = ftello(file_);
#endif  // _WIN32
  if (pos < 0) {
    LOG_SYSERROR(ftell) << "file_path:" << file_path_;
    return -1;
  }
  return pos;
}

int64_t File::Length() {
  FILE_CHECK_FILEPTR(-1);
  int64_t cur = Tell();
  if (!Seek(0, SEEK_END)) return -1;
  int64_t length = Tell();
  Seek(cur, SEEK_SET);
  return length;
}

bool File::Read(void* out_buff, size_t size) {
  FILE_CHECK_FILEPTR(false);
  size_t readn = fread(out_buff, size, 1, file_);
  if (readn != 1 && ferror(file_)) {
    LOG_SYSERROR(fread) << "file_path:" << file_path_;
    return false;
  }
  return true;
}

bool File::Write(const void* buff, size_t size, bool flush) {
  FILE_CHECK_FILEPTR(false);
  if (fwrite(buff, size, 1, file_) != 1) {
    LOG_SYSERROR(fwrite) << "file_path:" << file_path_;
    return false;
  }
  if (flush) {
    return Flush();
  }
  return true;
}

bool File::Load(std::string& out_data) {
  int64_t length = Length();
  if (length < 0) return false;
  rewind(file_);

  size_t size = static_cast<size_t>(length);
  out_data.resize(size + 1);  // +1用于保证读过EOF字符
  size_t allread = 0;
  while (!feof(file_)) {
    size_t readn =
        fread(&out_data[0] + allread, 1, out_data.size() - allread, file_);
    if (ferror(file_)) {
      LOG_SYSERROR(fread) << "file_path:" << file_path_;
      return false;
    }
    allread += readn;
  }
  out_data.resize(allread);  // windows系统读到内存时会把换行符"\r\n"转换成"\n"
  return true;
}

bool File::Flush() {
  FILE_CHECK_FILEPTR(false);
  if (fflush(file_) != 0) {
    LOG_SYSERROR(fflush) << "file_path:" << file_path_;
    return false;
  }
  return true;
}

}  // namespace xlcomm